#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ======================================================================
#    Copyright (C) 2006 Robert Muth <robert at muth dot org>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; version 2 of June 1991.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this yesprogram in form of the file COPYING;
#    if not, write to the
#
#    Free Software Foundation, Inc. <http://www.fsf.org>
#    59 Temple Place, Suite 330,
#    Boston, MA 02111-1307  USA
#
# ======================================================================

"""yes
ui/toplevel component for pytrommler

http://muth.org/Robert/pyTrommler
"""
#======================================================================

# python imports (alphabetical)
import gobject
import gtk
import logging
import numpy
import os
import pygame
import sys

# local imports (alphabetical)
import song
import player

# ======================================================================
# NOTE(muth): we have another player thread running
# we need this to make sure it actually runs
gobject.threads_init()

# ======================================================================
# I18N
# ======================================================================
DOMAIN='pytrommler'
LOCALE='locale'
import gettext
print gettext.find(DOMAIN, LOCALE)
gettext.install(DOMAIN, LOCALE)
# ======================================================================

_Tooltips = gtk.Tooltips()
_Tooltips.enable()

_Dirty = None
_MainWindow = None
_Player = None

MEASURES_PER_ROW = 16

# ======================================================================
def SetDirty():
    global _Dirty
    if not _Dirty:
        logging.info("data is now dirty")
        _Dirty = 1
    return

def ClearDirty():
    global _Dirty
    if _Dirty:
        logging.info("data is now clean")
        _Dirty = None
    return

def CheckDirtySet():
    global _Dirty
    if not _Dirty: return 0
    result = MyDialog(_("Warning"),
                      _("The applications has unsaved data:\n"),
                      [(_("cancel operation"), 1), (_("continue"), 0)])
    if not result:
        ClearDirty()
    return _Dirty


def ConfirmedQuit(dummy_widget, dummy_event):
    logging.info("attempt to quit trommler")
    if CheckDirtySet():
        return 1
    else:
        _Player.kill()
        _MainWindow.destroy()
        gtk.main_quit()
        return 0

def GetFilenameViaFileselector(title, act, extension=None):
    """
    Get filename from  user via Fileselector dialog.
    Return pathname or empty string if canceled
    """
    if act == gtk.FILE_CHOOSER_ACTION_SAVE:
        action_button = gtk.STOCK_SAVE
    else:
        action_button = gtk.STOCK_OPEN
    dialog = gtk.FileChooserDialog(title=title,
                                   parent=_MainWindow,
                                   action=act,
                                   buttons=(gtk.STOCK_CANCEL,
                                            gtk.RESPONSE_CANCEL,
                                            action_button,
                                            gtk.RESPONSE_OK))
    if extension:
        filter = gtk.FileFilter()
        filter.add_pattern(extension)
        dialog.set_filter(filter)
    response = dialog.run()
    if response == gtk.RESPONSE_OK:
        filename =  dialog.get_filename()
    else:
        filename = ""
    dialog.destroy()
    logging.info("file selected [%s]", filename)
    return filename

# ======================================================================

def MyMenuItem(text, action=None, data=None):
    item = gtk.MenuItem(text)
    item.show()
    if action: item.connect('activate', action, data)
    return item

def MyMenuTitle(name, tt=None):
    menutitle = gtk.MenuItem(name)
    menutitle.show()
    if tt: _Tooltips.set_tip(menutitle, tt)
    return menutitle

def MyLabel(text):
    label = gtk.Label(text)
    label.show()
    return label

def MyFiller():
    label = gtk.Label("   ")
    label.show()
    return label

def MyButton(name, tt=None, action=None, data=None):
    button = gtk.Button(name)
    button.show()
    if tt: _Tooltips.set_tip(button, tt)
    if action: button.connect('clicked', action, data)
    return button

def MyRadioButton(name, group, tt=None, action=None, data=None):
    button = gtk.RadioButton(group, name)
    button.show()
    if tt: _Tooltips.set_tip(button, tt)
    if action: button.connect('clicked', action, data)
    return button

def MyToggleButton(name, tt=None, action=None, data=None):
    button = gtk.ToggleButton(name)
    button.show()
    if tt:  _Tooltips.set_tip(button, tt)
    if action: button.connect('clicked', action, data)
    return button

def MyHSeparator():
    sep = gtk.HSeparator()
    sep.show()
    return sep

def MyMenuSeparator():
    sep = gtk.SeparatorMenuItem()
    sep.show()
    return sep

def MySpinButton(adj, tt=None, action=None, data=None, digits=0):
    spin = gtk.SpinButton(adj, 1, digits)
    spin.show()
    if tt: _Tooltips.set_tip(spin, tt)
    if action: spin.connect('value-changed', action, data)
    return spin

def MyEntry(width, editable, tt=None, action=None, data=None):
    entry = gtk.Entry()
    entry.show()
    entry.set_editable(editable)
    entry.set_width_chars(width)
    if tt: _Tooltips.set_tip(entry, tt)
    if action:
        entry.connect('activate', action, data)
        entry.connect('focus_out_event', action, data)
    return entry

def MyCheckButton(name, active, mode, tt=None, action=None, data=None):
    button = gtk.CheckButton(name)
    button.show()
    button.set_active(active)
    button.set_mode(mode)
    if tt: _Tooltips.set_tip(button, tt)
    if action: button.connect('toggled', action, data)
    return button

# ======================================================================
def MyDialog(title, message, buttons):
    ret = [0] # captured in closure

    dialog = gtk.Dialog()
    if _MainWindow:
        dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
        dialog.set_transient_for(_MainWindow)

    label = gtk.Label(message)
    dialog.vbox.pack_start(label)

    def ActionButtonPressed(dummy, a):
        dialog.destroy()
        dialog.response(0)
        ret[0] = a
        if callable(a): a()
        return

    for (b, a) in buttons:
        button = gtk.Button(b)
        dialog.action_area.add(button)
        button.connect("clicked", ActionButtonPressed, a)

    dialog.set_title(title)
    dialog.show_all()
    #dialog.move(500, 500)

    dialog.run()
    return ret[0]

# ======================================================================
kVersion = "$Revision: 258 $"

kCopyright = "Copyright (c) 2006 and onwards Robert Muth"

kAbout = """
%s

pyTrommler comes with
ABSOLUTELY NO WARRANTY;
for details see  the file 'COPYING'.
This is free software, and you are welcome
to redistribute it under certain conditions;
for details see  the file '00copying'

Email comments and suggestions to:
robert at muth dot org

version: %s
platform: %s
py version: %s
gtk version:  %s
pygtk version: %s
pygame version: %s
sdl version: %s
numpy: %s
""" % (kCopyright,
       kVersion,
       sys.platform,
       sys.version.split()[0],
       str(gtk.gtk_version),
       str(gtk.pygtk_version),
       str(pygame.vernum),
       str(pygame.get_sdl_version()),
       numpy.version.version,
       )

def ActionAbout(dummy, dummy_data):
    logging.info("about dialog opened")
    MyDialog(_("About pyTrommler"),
             kAbout,
             [(_("OK"), None)])
    return
# ======================================================================
def ActionQuit(dummy_widget, dummy_data):
    ConfirmedQuit(None, None)
    return

# ======================================================================
def style_dumper(style):
    print style.bg[gtk.STATE_NORMAL]
    print style.bg[gtk.STATE_PRELIGHT]
    print style.bg[gtk.STATE_ACTIVE]

    print style.bg_gc[gtk.STATE_NORMAL]
    print style.bg_gc[gtk.STATE_PRELIGHT]
    print style.bg_gc[gtk.STATE_ACTIVE]
    return

class TrommlerGui:

    def CreateEvenOddButtonStyles(self):
        global _MainWindow
        # only windows seem to have fully initiaslized style objects

        style = gtk.Button().get_style()

        even_style = style.copy()
        odd_style = style.copy()
        cmap = _MainWindow.get_colormap()
        red = cmap.alloc_color("red") # light red
        yellow = cmap.alloc_color("yellow") # light red
        blue = cmap.alloc_color("blue") # light red

        even_style.bg[gtk.STATE_NORMAL] = red
        even_style.bg[gtk.STATE_PRELIGHT] = red
        even_style.bg[gtk.STATE_ACTIVE] = blue

        odd_style.bg[gtk.STATE_NORMAL] = yellow
        odd_style.bg[gtk.STATE_PRELIGHT] = yellow
        odd_style.bg[gtk.STATE_ACTIVE] = blue

        return [even_style, odd_style]



    def __init__(self, song, player, drum_path):
        self._drum_path = drum_path
        self._song = song
        self._player = player
        self._current_pattern_no = 0
        self._current_from_pattern_no = 0
        self._current_drum_no = 0
        self._MeasureMenuReuse = None
        self._MenuDrumReuse = None
        self._beat_button_style = None

        self._widget_beats = []
        self._widget_drum_name = []
        self._widget_drum_volume = []
        self._widget_drum_panning = []
        self._widget_drum_mute = []
#        self.UpdateBeats()
        return

    def current_pattern(self):
        return self._song.pattern(self._current_pattern_no)

    def ActionNewSong(self, dummy_widget, dummy_data):
        logging.info("new song")
        if CheckDirtySet(): return
        self._song.clear()
        self._song.set_path("")
        self.UpdatePattern()
        self.UpdateDrums()
        self.UpdateSong()
        return

    def ActionLoadSong(self, dummy_widget, dummy_data):
        logging.info("load song")
        if CheckDirtySet(): return
        filename = GetFilenameViaFileselector(_("Load song"),
                                              gtk.FILE_CHOOSER_ACTION_OPEN,
                                              "*.sng")
        if not filename: return
        self._song.set_path(filename)
        try:
            inf = file(filename)
            data = inf.read()
            inf.close()
        except Exception, err:
            logging.error(str(err))
        # TODO(muth): better exception handling
        self._song.deserialize(data, self._drum_path)
        self._song.set_path(filename)
        self.UpdatePattern()
        self.UpdateDrums()
        self.UpdateSong()
        ClearDirty()
        err = player.LoadAllSongDrumSamples(self._song)
        if not err: return
        MyDialog(_("Error loading one or more drum samples"),
                 err,
                 [(_("OK"), None)])
        return

    def ActionSaveSong(self, dummy_widget, save_as):
        logging.info("save song  %d", save_as)
        if save_as or not self._song.path():
            filename = GetFilenameViaFileselector(_("Save song as"),
                                                  gtk.FILE_CHOOSER_ACTION_SAVE,
                                                  "*.sng")
            if not filename:
                logging.info("no filename selected")
                return
            # TODO(muth): check for overwrite of existing file
            self._song.set_path(filename)

        data = self._song.serialize(self._drum_path)
        try:
            fout = file(self._song.path(), "wb")
            fout.write(data)
            fout.close()
            ClearDirty()
        except Exception, err:
            logging.error(str(err))
        # TODO(muth): better exception handling
        return

    def ActionSaveSample(self, dummy_widget, mode):
        def RangeCheck(start, stop):
            if not 0 <= start < self._song.length():
                return _("bad start range")
            elif not 0 <= stop < self._song.length():
                return _("bad stop range")
            elif not start <= stop:
                return _("inconsistent start/stop range")
            else:
                return ""

        logging.info("save sample  mode:  %s", mode)
        filename = GetFilenameViaFileselector(_("Save sample as"),
                                              gtk.FILE_CHOOSER_ACTION_SAVE,
                                              "*.wav")
        if not filename:
            logging.info("no filename selected")
            return
        recorder = player.RecordingPlayer()
        if mode == "song":
            player.PlaySong(recorder, self._song)
        elif mode == "pattern":
            player.PlayPattern(recorder, self._song, self._current_pattern_no)
        elif mode == "range":
            start = self._widget_range_start.get_value_as_int()
            stop = self._widget_range_stop.get_value_as_int()
            err = RangeCheck(start, stop)
            if err:
                logging.error(err)
                return
            player.PlaySong(recorder, self._song, start, stop)
        else:
            assert 0
        data = recorder.get_wave()
        try:
            fout = file(filename, "wb")
            fout.write(data)
            fout.close()
        except Exception, err:
            logging.error(str(err))
        return


    def CreateMainMenu(self):
        hbox = gtk.HBox()
        hbox.show()
        hbox.pack_start(MyButton(_(" Quit "), "", action=ActionQuit))
        hbox.pack_start(MyButton(_(" About "), "", action=ActionAbout))

        hbox.pack_start(MyFiller())
        hbox.pack_start(MyButton(_(" New Song "), "",
                                 action=self.ActionNewSong))
        hbox.pack_start(MyButton(_(" Load Song "), "",
                                 action=self.ActionLoadSong))
        hbox.pack_start(MyButton(_(" Save Song "), "",
                                 action=self.ActionSaveSong, data=0))
        hbox.pack_start(MyButton(_(" Save Song As"), "",
                                 action=self.ActionSaveSong, data=1))

        hbox.pack_start(MyLabel(_(" Save Sample ")))
        hbox.pack_start(MyButton(_(" Pattern "),
                                 action=self.ActionSaveSample, data="pattern"))
        hbox.pack_start(MyButton(_(" Range "),
                                 action=self.ActionSaveSample, data="range"))
        hbox.pack_start(MyButton(_(" Song "),
                                 action=self.ActionSaveSample, data="song"))
        return hbox

    # ======================================================================
    def ActionChangeMode(self, dummy_widget, data):
        logging.info("setting playmode to %s", data)
        self._player.set_mode(data)
        return

    def ActionPlay(self, dummy_widget, dummy_data):
        logging.info("toggle play")
        self._player.toggle_play()
        return

    def ActionLoop(self, widget, dummy_data):
        value = widget.get_active()
        logging.info("set looping %d", value)
        self._player.set_loop(value)
        return

    def ActionReset(self, dummy_widget, dummy_data):
        logging.info("reset")
        self._player.reset()
        return

    def UpdateCurrentBeatLabel(self, measure, pattern, beat):
        """
        may be called from other threads
        """
        gtk.gdk.threads_enter()
        self._current_beat_label.set_text("%03d:%03d:%03d" %
                                          (measure, pattern, beat))
        gtk.gdk.threads_leave()
        return

    def CreatePlayMenu(self):
        hbox = gtk.HBox()
        hbox.show()
        # LEFT
        button = MyRadioButton(_("Pattern"), None,
                               action=self.ActionChangeMode, data="pattern")
        hbox.pack_start(button)
        button = MyRadioButton(_("Range"), button,
                               action=self.ActionChangeMode, data="range")
        hbox.pack_start(button)
        button = MyRadioButton(_("Song"), button,
                               action=self.ActionChangeMode, data="song")
        hbox.pack_start(button)

        # RIGHT
        #hbox.pack_end(MyFiller())
        button = MyCheckButton(_(" Loop "), 0, 1, action=self.ActionLoop)
        hbox.pack_end(button)

        hbox.pack_end(MyFiller())
        label = MyLabel("")
        hbox.pack_end(label)
        self._current_beat_label = label
        self.UpdateCurrentBeatLabel(0, 0, 0)
        hbox.pack_end(MyFiller())
        hbox.pack_end(MyButton(_(" Reset "), "", action=self.ActionReset))
        hbox.pack_end(MyFiller())
        button = MyButton(_(" Start/Stop "), "", action=self.ActionPlay)
        hbox.pack_end(button)
        return hbox

    # ======================================================================
    def UpdatePatternBeats(self):
        logging.info("updating pattern beats")
        if not self._beat_button_style:
            self._beat_button_style = self.CreateEvenOddButtonStyles()

        num_beats = self.current_pattern().num_beats()
        for d in range(song.MAX_DRUM_NUM):
            for i in range(song.MAX_BEAT_NUM):
                b = self._widget_beats[d][i]
                if i >= num_beats:
                    b.hide()
                else:
                    b.set_active(self.current_pattern().beat(d, i))
                    style_index = (i/self.current_pattern().beats_per_unit())%2
                    b.set_style(self._beat_button_style[style_index])
                    b.show()
        return

    def UpdatePatternAttributes(self):
        logging.info("updating pattern beats")
        pattern = self.current_pattern()
        self._widget_pattern_name.set_text(pattern.name())
        adj = self._widget_pattern_beats_per_units.get_adjustment()
        adj.set_value(pattern.beats_per_unit())
        self._widget_pattern_units.set_value(pattern.units())
        return

    # ======================================================================
    def UpdatePattern(self):
        self.UpdatePatternAttributes()
        self.UpdatePatternBeats()
        return
    # ======================================================================
    def ActionUnitsChanged(self, widget, dummy_data):
        value = widget.get_value_as_int()
        logging.info("units changed to %d", value)
        SetDirty()
        self.current_pattern().set_units(value)
        self.UpdatePatternBeats()
        return

    # ======================================================================
    def ActionBPUsChanged(self, widget, dummy_data):
        value = widget.get_value_as_int()
        logging.info("bpus changed to %d", value)
        SetDirty()
        self.current_pattern().set_beats_per_unit(value)
        self.UpdatePatternBeats()
        return

    def ActionPatternRotate(self, widget, direction):
        logging.info("pattern rotated %d", direction)
        SetDirty()
        self.current_pattern().rotate_left(direction)
        self.UpdatePatternBeats()
        return

    def ActionPatternNumChanged(self, widget, dummy_data):
        value = widget.get_value_as_int()
        logging.info("pattern changed to %d", value)
        SetDirty()
        self._current_pattern_no = value
        self._player.set_pattern_no(value)
        self.UpdatePattern()
        return

    def ActionPatternName(self, widget, data, dummy=None):
        text = widget.get_text()
        logging.info("pattern name changed to %s", text)
        SetDirty()
        self.current_pattern().set_name(text)
        return

    def ActionFromPatternNumChanged(self, widget, dummy_data):
        value = widget.get_value_as_int()
        logging.info("from pattern changed to %d", value)
        self._current_from_pattern_no = value
        name = self._song.pattern(value).name()
        self._widget_from_pattern_name.set_text(name)
        return

    def ActionCopyPattern(self, widget, dummy_data):
        logging.info("copy pattern from %d", self._current_from_pattern_no)
        SetDirty()
        self._song.pattern_copy(self._current_from_pattern_no,
                                self._current_pattern_no)
        self.UpdatePattern()
        return
    # ======================================================================
    def CreatePatternMenu(self):
        hbox = gtk.HBox()
        hbox.show()
        hbox.pack_start(MyLabel(_("Pattern:")))
        adj = gtk.Adjustment(0, 0, 99, 1)
        hbox.pack_start(MySpinButton(adj, action=self.ActionPatternNumChanged))

        self._widget_pattern_name = MyEntry(16, 1, action=self.ActionPatternName)
        hbox.pack_start(self._widget_pattern_name)

        hbox.pack_start(MyLabel(_("Units:")))
        adj = gtk.Adjustment(4, 1, 10, 1)
        self._widget_pattern_units = MySpinButton(adj, action=self.ActionUnitsChanged)
        hbox.pack_start(self._widget_pattern_units)

        hbox.pack_start(MyLabel(_(" BPUs:")))
        adj = gtk.Adjustment(4, 1, 10, 1)
        self._widget_pattern_beats_per_units = MySpinButton(adj, action=self.ActionBPUsChanged)
        hbox.pack_start(self._widget_pattern_beats_per_units)
        hbox.pack_start(MyFiller())

        hbox.pack_start(MyButton(_(" << "),
                                 action=self.ActionPatternRotate, data=1))
        hbox.pack_start(MyLabel(_(" Rotate ")))
        hbox.pack_start(MyButton(_(" >> "),
                                 action=self.ActionPatternRotate, data=-1))
        hbox.pack_start(MyFiller())

        hbox.pack_start(MyButton(_(" Copy From "),
                                 action=self.ActionCopyPattern))
        adj = gtk.Adjustment(0, 0, 99, 1)
        hbox.pack_start(MySpinButton(adj,
                                     action=self.ActionFromPatternNumChanged))

        self._widget_from_pattern_name = MyEntry(16, 0)
        hbox.pack_start(self._widget_from_pattern_name)
        return hbox

    # ======================================================================
    # ======================================================================
    def UpdateDrum(self, drum_no):
        drum = self._song.drum(drum_no)
        self._widget_drum_name[drum_no].set_text(drum.name())
        self._widget_drum_panning[drum_no].set_value(drum.panning())
        self._widget_drum_volume[drum_no].set_value(drum.volume())
        self._widget_drum_mute[drum_no].set_active(drum.mute())
        return

    def UpdateDrums(self):
        for i in range(song.MAX_DRUM_NUM): self.UpdateDrum(i)
        return

    def ActionDrumLoad(self, dummy_widget, set_name):
        logging.info("drum load %d, set name %d",
                     self._current_drum_no, set_name)
        filename = GetFilenameViaFileselector(_("Load drum"),
                                              gtk.FILE_CHOOSER_ACTION_OPEN)
        if not filename: return
        drum = self._song.drum(self._current_drum_no)
        drum.set_path(filename)
        err = player.LoadOneSongDrumSamples(self._song, self._current_drum_no)
        if err:
            MyDialog(_("Error loading drum samples"),
                     err,
                     [(_("OK"), None)])
        else:
            if set_name:
                drum.set_name(os.path.split(filename)[1])
                self.UpdateDrum(self._current_drum_no)
                SetDirty()
        return

    def ActionDrumMove(self, dummy_widget, direction):
        logging.info("drum move, direction %d", direction)
        SetDirty()
        if direction != 0:
            drum = self._current_drum_no + direction
            if drum < 0: return
            if drum >= song.MAX_DRUM_NUM: return
        else:
            drum = 0
        self._song.drum_swap(drum, self._current_drum_no)
        self.UpdateDrum(drum)
        self.UpdateDrum(self._current_drum_no)
        return

    def CreateDrumPopUp(self, title, drum_no):
        menubar = gtk.MenuBar()
        menubar.show()
        menutitle = MyMenuTitle(title)
        menubar.append(menutitle)
        if self._MenuDrumReuse:
            menu = self._MenuDrumReuse
        else:
            menu = gtk.Menu()
            menu.append(MyMenuItem(_("Drum Name")))
            menu.append(MyMenuItem(_("Path")))
            menu.append(MyMenuSeparator())
            menu.append(MyMenuItem(_("Load"),
                                   action=self.ActionDrumLoad, data=0))
            menu.append(MyMenuItem(_("Load And Set Name"),
                                   action=self.ActionDrumLoad, data=1))
            menu.append(MyMenuSeparator())
            menu.append(MyMenuItem(_("Swap First"),
                                   action=self.ActionDrumMove, data=0))
            menu.append(MyMenuItem(_("Move Up"),
                                   action=self.ActionDrumMove, data=-1))
            menu.append(MyMenuItem(_("Move Down"),
                                   action=self.ActionDrumMove, data=+1))
            menu.append(MyMenuSeparator())
            menu.append(MyMenuItem(_("Clear Beats")))

            self._MenuDrumReuse = menu
        menutitle.set_submenu(menu)

        def ActionPatchMenu(dummy, drum_no):
            logging.info("drum menu %d opened", drum_no)
            self._current_drum_no = drum_no
            children = menu.get_children()
            drum = self._song.drum(drum_no)
            children[0].get_child().set_text(drum.name())
            children[1].get_child().set_text(drum.path())
            return
        menutitle.connect('activate', ActionPatchMenu, drum_no)
        return menubar

    # ======================================================================
    def ActionBeatClicked(self, button, pos):
        val = button.get_active()
        logging.info("beat changed drum %d pos %d  val %d",
                     pos[0], pos[1], val)
        SetDirty()
        self.current_pattern().set_beat(pos[0], pos[1], val)
        return

    def ActionDrumNameChanged(self, widget, dummy, drum_no):
        text = widget.get_text()
        logging.info("drum %d name changed to %s", drum_no, text)
        SetDirty()
        self._song.drum(drum_no).set_name(text)
        return

    def ActionDrumMuteChanged(self, widget, drum_no):
        value = widget.get_active()
        logging.info("drum %d mute changed to %d", drum_no, value)
        SetDirty()
        self._song.drum(drum_no).set_mute(value)
        return

    def ActionDrumVolumeChanged(self, widget, drum_no):
        value = widget.get_value()
        logging.info("drum %d volume changed to %f", drum_no, value)
        SetDirty()
        self._song.drum(drum_no).set_volume(value)
        return

    def ActionDrumPanningChanged(self, widget, drum_no):
        value = widget.get_value()
        logging.info("drum %d panning changed to %f", drum_no, value)
        SetDirty()
        self._song.drum(drum_no).set_panning(value)
        return
    # ======================================================================
    def CreateBeats(self):
        table = gtk.Table(song.MAX_DRUM_NUM, song.MAX_BEAT_NUM+7, 0)
        table.show()
        for i in range(song.MAX_DRUM_NUM):
            table.attach(self.CreateDrumPopUp("%02d" % i, i), 0, 1, i, i+1, 0, 0, 0, 0)

            adj = gtk.Adjustment(1.0, 0, 10.0, 0.01)
            button = MySpinButton(adj, action=self.ActionDrumVolumeChanged, data=i, digits=2)
            self._widget_drum_volume.append(button)
            table.attach(button, 1, 2, i, i+1, 0, 0, 0, 0)

            adj = gtk.Adjustment(0, -1.0, 1.0, 0.01)
            button = MySpinButton(adj, action=self.ActionDrumPanningChanged, data=i, digits=2)
            self._widget_drum_panning.append(button)
            table.attach(button, 2, 3, i, i+1, 0, 0, 0, 0)

            button = MyCheckButton("", 1, 1, action=self.ActionDrumMuteChanged, data=i)
            self._widget_drum_mute.append(button)
            table.attach(button, 3, 4, i, i+1, 0, 0, 0, 0)

            entry = MyEntry(16, 1, action=self.ActionDrumNameChanged, data=i)
            self._widget_drum_name.append(entry)
            table.attach(entry, 4, 5, i, i+1, 0, 0, 0, 0)

            self.UpdateDrum(i)
            beats = []
            for j in range(song.MAX_BEAT_NUM):
                button = MyToggleButton(" ", action=self.ActionBeatClicked, data=(i, j))
                #button.set_usize(17, 17)
                beats.append(button)
                #button.set_usize(15,15)
                table.attach(button, 5+j, 6+j, i, i+1, 0, 0, 0, 0)
            self._widget_beats.append(beats)
        return table

    # ======================================================================
    def CreatePatternEditor(self):
        vbox = gtk.VBox()
        vbox.show()
        vbox.pack_start(self.CreatePatternMenu(), 0)
        window = gtk.ScrolledWindow()
        window.show()
        vbox.pack_start(window)
        window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        window.add_with_viewport(self.CreateBeats())
        return vbox

    # ======================================================================
    def ActionSongNameChanged(self, widget, dummy, dummy2):
        text = widget.get_text()
        logging.info("song name changed to %s", text)
        SetDirty()
        self._song.set_name(text)
        return

    def ActionSongLengthChanged(self, widget, dummy):
        value = widget.get_value_as_int()
        logging.info("song length changed to %d", value)
        SetDirty()
        self._song.set_length(value)
        self.UpdateMeasures()
        return

    def ActionSongUpmsChanged(self, widget, dummy):
        value = widget.get_value_as_int()
        logging.info("song upms changed to %d", value)
        SetDirty()
        self._song.set_units_per_minute(value)
        return

    def UpdateMeasures(self):
        logging.info("updating measures")
        for i in range(song.MAX_MEASURE_NUM):
            vbox = self._widget_measures[i].parent
            if i < self._song.length():
                self._widget_measures[i].set_text(str(self._song.measure(i)))
                vbox.show()
            else:
                vbox.hide()
        return

    def UpdateSong(self):
        logging.info("updating song attributes")
        self._widget_song_name.set_text(self._song.name())
        self._widget_song_length.set_value(self._song.length())
        self._widget_song_upms.set_value(self._song.units_per_minute())
        return

    def ActionRangeChanged(self, dummy_widget, dummy_data):
        """
        this code is a total hack, it triggers new
        eventes while processing older ones
        """
        stop = self._widget_range_stop.get_value_as_int()
        start = self._widget_range_start.get_value_as_int()
        logging.info("range changed: %d - %d (%d)",
                     start, stop, self._song.length())

        max_start = min(self._song.length() - 1, stop)
        assert start >= 0
        if start > max_start:
            self._widget_range_start.set_value(max_start)
            return 0
        elif stop < start:
            self._widget_range_stop.set_value(start)
            return 0
        elif stop >= self._song.length():
            self._widget_range_stop.set_value(self._song.length() - 1)
            return 0
        else:
            self._player.set_range(start, stop)
            return 1
        return


    def CreateSongMenu(self):
        hbox = gtk.HBox()
        hbox.show()
        # LEFT
        hbox.pack_start(MyLabel(_("Song:")), 0)
        entry = MyEntry(32, 1, action=self.ActionSongNameChanged)
        self._widget_song_name = entry
        hbox.pack_start(entry, 0)
        hbox.pack_start(MyFiller(), 0)

        hbox.pack_start(MyLabel(_( "Measures:")), 0)
        adj = gtk.Adjustment(32, 1, song.MAX_MEASURE_NUM, 1)
        spin_button = MySpinButton(adj, action=self.ActionSongLengthChanged)
        self._widget_song_length = spin_button
        hbox.pack_start(spin_button, 0)
        hbox.pack_start(MyFiller(), 0)

        adj = gtk.Adjustment(60, 1, player.MAX_UNITS_PER_MINUTE, 1)
        hbox.pack_start(MyLabel(_( "UPMs:")), 0)
        spin_button = MySpinButton(adj, "", action=self.ActionSongUpmsChanged)
        self._widget_song_upms = spin_button
        hbox.pack_start(spin_button, 0)

        # RIGHT
        # hbox.pack_end(MyFiller())
        adj = gtk.Adjustment(31, 0, song.MAX_MEASURE_NUM, 1)
        spin_button = MySpinButton(adj, "", action=self.ActionRangeChanged)
        self._widget_range_stop = spin_button
        hbox.pack_end(spin_button, 0)
        hbox.pack_end(MyLabel(_(" to ")), 0)
        adj = gtk.Adjustment(0, 0, song.MAX_MEASURE_NUM, 1)
        spin_button = MySpinButton(adj, "", action=self.ActionRangeChanged)
        self._widget_range_start = spin_button
        hbox.pack_end(spin_button, 0)
        hbox.pack_end(MyLabel(_( "Range:")), 0)
        return hbox

    # ======================================================================
    def ActionMeasureShowPattern(self, dummy_widget, dummy_data):
        measure_no = self._current_measure_no
        pattern_no = self._song.measure(measure_no)
        logging.info("show pattern %d for measure %d",
                     pattern_no, measure_no)

        self._current_pattern_no = pattern_no
        self.UpdatePattern()
        return

    def ActionMeasureSetCurrentPattern(self, dummy_widget, dummy_data):
        measure_no = self._current_measure_no
        logging.info("setting pattern %d for measure %d",
                     self._current_pattern_no, measure_no)
        self._song.set_measure(measure_no, self._current_pattern_no)
        self.UpdateMeasures()
        return

    def ActionMeasureSetRangeStartStop(self, dummy_widget, is_start):
        measure_no = self._current_measure_no
        logging.info("setting measure %d  as range %s",
                     measure_no, is_start and "start" or "stop")
        if is_start:
            self._widget_range_start.set_value(measure_no)
        else:
            self._widget_range_stop.set_value(measure_no)
        return

    # ======================================================================
    def CreateMeasurePopUp(self, title, data):
        global _MenuReuse
        menubar = gtk.MenuBar()
        menubar.show()
        menutitle = MyMenuTitle(title)
        menubar.append(menutitle)
        if self._MeasureMenuReuse:
            menu = self._MeasureMenuReuse
        else:
            menu = gtk.Menu()
            menu.append(MyMenuItem(_("Pattern")))
            menu.append(MyMenuSeparator())
            menu.append(MyMenuItem(_("Show Pattern"),
                                   action=self.ActionMeasureShowPattern))
            menu.append(MyMenuItem(_("Set Current Pattern"),
                                   action=self.ActionMeasureSetCurrentPattern))
            menu.append(MyMenuSeparator())
            menu.append(MyMenuItem(_("Duplicate Measure")))
            menu.append(MyMenuItem(_("Delete Measure")))
            menu.append(MyMenuSeparator())
            menu.append(MyMenuItem(_("Set Range Start"),
                                   action=self.ActionMeasureSetRangeStartStop, data=1))
            menu.append(MyMenuItem(_("Set Range End"),
                                   action=self.ActionMeasureSetRangeStartStop, data=0))
            self._MeasureMenuReuse = menu

        def ActionPatchMenu(dummy, measure_no):
            logging.info("measure menu %d opened", measure_no)
            self._current_measure_no = measure_no
            children = menu.get_children()
            pattern = self._song.pattern(self._song.measure(measure_no))
            children[0].get_child().set_text(pattern.name())
            return

        menutitle.set_submenu(menu)
        menutitle.connect('activate', ActionPatchMenu, data)
        return menubar

    # ======================================================================
    def ActionMeasureChanged(self, widget, measure_no, dummy=0):
        text = widget.get_text()
        logging.info("changing measure %d to [%s]", measure_no, text)
        try:
            self._song.set_measure(measure_no, int(text))
        except Exception, err:
            logging.error(err)
        return

    # ======================================================================
    def CreateMeasures(self):
        vbox = gtk.VBox()
        vbox.show()
        self._widget_measures = []
        for j in range(0, song.MAX_MEASURE_NUM, MEASURES_PER_ROW):
            hbox = gtk.HBox()
            hbox.show()
            vbox.pack_start(hbox, 0)

            for i in range(MEASURES_PER_ROW):
                num = i + j
                vbox2 = gtk.VBox()
                vbox2.show()
                hbox.pack_start(vbox2, 0)
                vbox2.pack_start(self.CreateMeasurePopUp("%03d" % num, num), 0)
                entry = MyEntry(3, 1, action=self.ActionMeasureChanged, data=num)
                self._widget_measures.append(entry)
                vbox2.pack_start(entry, 0)
        return vbox

    # ======================================================================
    def CreateSongEditor(self):
        vbox = gtk.VBox()
        vbox.show()
        vbox.pack_start(self.CreateSongMenu(), 0)
        window = gtk.ScrolledWindow()
        window.show()
        vbox.pack_start(window)
        window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        window.add_with_viewport(self.CreateMeasures())
        return vbox

    # ======================================================================
    def CreateTopLevel(self, have_sound):
        global _MainWindow, _Tooltips

        window = gtk.Window()
        window.connect("delete_event", ConfirmedQuit)
        _MainWindow = window
        window.set_title('pyTrommler')
        window.show()

        vbox = gtk.VBox()
        vbox.show()
        vbox.pack_start(self.CreateMainMenu(), 0)
        vbox.pack_start(MyHSeparator(), 0)
        vpaned = gtk.VPaned()
        vpaned.add1(self.CreatePatternEditor())
        vpaned.add2(self.CreateSongEditor())
        vpaned.set_position(200)
        vpaned.show()

        vbox.pack_start(vpaned)

        vbox.pack_start(MyHSeparator(), 0)
        if have_sound:
            vbox.pack_start(self.CreatePlayMenu(), 0)

        window.add(vbox)
        self.UpdatePattern()
        self.UpdateSong()
        self.UpdateMeasures()
        ClearDirty()
        return

# ======================================================================
if __name__ == '__main__':
    debug = 1
    logformat = ("%(levelname)s:"
                 "%(module)s:"
                 "%(lineno)d:"
                 "%(threadName)s:"
                 "%(message)s")

    if debug:
        logging.basicConfig(level=logging.INFO, format=logformat)
    else:
        logging.basicConfig(level=logging.WARNING, format=logformat)
    s = song.Song()
    p = player.AudioPlayer()
    g = player.GuiPlayer(s, p)
    _Player = g
    gui = TrommlerGui(s, g, os.getenv("TROMMLER_DRUM_DIR", "Test"))
    gui.CreateTopLevel(1)
    g.set_callback(gui.UpdateCurrentBeatLabel)
    try:
        gtk.main()
    except KeyboardInterrupt:
        pass


